using System;
using System.Xml;
using System.Globalization;


namespace Waymex.Gps
{
	/// <summary>
	/// This Trackpoint object forms part of a Tracklog.
	/// </summary>
	/// <remarks>
    /// <para><b>Magellan Devices</b></para>
    /// <para>The following Properties are used by the Magellan devices.</para>
    /// <list type="table">
	/// <item><term>Latitude</term><description>Double</description></item>
	/// <item><term>Longitude</term><description>Double</description></item>
	/// <item><term>FixDateTime</term><description>DateTime</description></item>
    /// </list>
    /// <br/><br/>
    /// <para><b>Garmin Devices</b></para>
    /// <para>
	/// In most cases not all of the Properties of the Trackpoint object are used. The Properties
	/// actually used are determined by the protocol employed by the connected GPS Device.
	/// Below is a list of which Properties are supported by Protocol. Please note that even though a
	/// Property is identified as being supported does not mean that it will actually be used/populated.
	/// </para>
	/// <br/><br/>
	/// <para>
	/// <b>Protocol D300</b>
	/// </para>
	/// <list type="table">
	/// <listheader><term>Property</term><description>Type</description></listheader>
	/// <item><term>TracklogNumber</term><description>Int16</description></item>
	/// <item><term>Latitude</term><description>Double</description></item>
	/// <item><term>Longitude</term><description>Double</description></item>
	/// <item><term>NewTrack</term><description>Boolean</description></item>
	/// </list>
	/// <para>
	/// The 'Time' Property provides a timestamp for the track point. This time
	/// is expressed as the number of seconds since UTC 24:00 on December 31st, 1989.
	/// </para>
	/// <para>
	/// When true, the 'NewTrack' Property indicates that the track log point marks
	/// the beginning of a new Tracklog segment.
	/// </para>
	/// <br/><br/>
	/// <para>
	/// <b>Protocol D301</b>
	/// </para>
	/// <list type="table">
	/// <listheader><term>Property</term><description>Type</description></listheader>
	/// <item><term>TracklogNumber</term><description>Int16</description></item>
	/// <item><term>Latitude</term><description>Double</description></item>
	/// <item><term>Longitude</term><description>Double</description></item>
	/// <item><term>NewTrack</term><description>Boolean</description></item>
	/// <item><term>Time</term><description>Int32</description></item>
	/// <item><term>Altitude</term><description>Single</description></item>
	/// <item><term>Depth</term><description>Single</description></item>
	/// </list>
	/// <para>
	/// The 'Time' Property provides a timestamp for the track point. This time
	/// is expressed as the number of seconds since UTC 24:00 on December 31st, 1989.
	/// </para>
	/// <para>
	/// When true, the 'NewTrack' Property indicates that the track log point marks
	/// the beginning of a new Tracklog segment.
	/// </para>
	/// <br/><br/>
	/// <para>
	/// <b>Protocol D302</b>
	/// </para>
	/// <list type="table">
	/// <listheader><term>Property</term><description>Type</description></listheader>
	/// <item><term>TracklogNumber</term><description>Int16</description></item>
	/// <item><term>Latitude</term><description>Double</description></item>
	/// <item><term>Longitude</term><description>Double</description></item>
	/// <item><term>NewTrack</term><description>Boolean</description></item>
	/// <item><term>Time</term><description>Int32</description></item>
	/// <item><term>Altitude</term><description>Single</description></item>
	/// <item><term>Depth</term><description>Single</description></item>
	/// <item><term>Temperature</term><description>Single</description></item>
	/// </list>
	/// <para>
	/// The 'Time' Property provides a timestamp for the track point. This time
	/// is expressed as the number of seconds since UTC 24:00 on December 31st, 1989.
	/// </para>
	/// <para>
	/// When true, the 'NewTrack' Property indicates that the track log point marks
	/// the beginning of a new Tracklog segment.
	/// </para>
	/// <para>
	/// The Temperature property may not be supported by all units. 
	/// A value of 1.0e25 in this field indicates that this parameter is not supported 
	/// or is unknown for this track point.
	/// </para>
    /// <br/><br/>
    /// <para>
    /// <b>Protocol D303</b>
    /// </para>
    /// <list type="table">
    /// <listheader><term>Property</term><description>Type</description></listheader>
    /// <item><term>TracklogNumber</term><description>Int16</description></item>
    /// <item><term>Latitude</term><description>Double</description></item>
    /// <item><term>Longitude</term><description>Double</description></item>
    /// <item><term>Time</term><description>Int32</description></item>
    /// <item><term>Altitude</term><description>Single</description></item>
    /// <item><term>HeartRate</term><description>Int32</description></item>
    /// </list>
    /// <para>
    /// The 'Time' Property provides a timestamp for the track point. This time
    /// is expressed as the number of seconds since UTC 24:00 on December 31st, 1989.
    /// </para>
    /// <para>
    /// The Temperature property may not be supported by all units. 
    /// A value of 1.0e25 in this field indicates that this parameter is not supported 
    /// or is unknown for this track point.
    /// </para>
    /// <para>
    /// The HeartRate is specified in beats per minute and is invalid if its value is 0.
    /// </para>
    /// <para>
    /// The position is invalid if both Latitude and Longitude values equal 0x7FFFFFFF.
    /// </para>
    /// <br/><br/>
    /// <para>
    /// <b>Protocol D304</b>
    /// </para>
    /// <list type="table">
    /// <listheader><term>Property</term><description>Type</description></listheader>
    /// <item><term>TracklogNumber</term><description>Int16</description></item>
    /// <item><term>Latitude</term><description>Double</description></item>
    /// <item><term>Longitude</term><description>Double</description></item>
    /// <item><term>Time</term><description>Int32</description></item>
    /// <item><term>Altitude</term><description>Single</description></item>
    /// <item><term>Distance</term><description>Single</description></item>
    /// <item><term>HeartRate</term><description>Int32</description></item>
    /// <item><term>Cadence</term><description>Int32</description></item>
    /// <item><term>Sensor</term><description>Boolean</description></item>
    /// </list>
    /// <para>
    /// The 'Time' Property provides a timestamp for the track point. This time
    /// is expressed as the number of seconds since UTC 24:00 on December 31st, 1989.
    /// </para>
    /// <para>
    /// The Temperature property may not be supported by all units. 
    /// A value of 1.0e25 in this field indicates that this parameter is not supported 
    /// or is unknown for this track point.
    /// </para>
    /// <para>
    /// The HeartRate is specified in beats per minute and is invalid if its value is 0.
    /// </para>
    /// <para>
    /// The position is invalid if both Latitude and Longitude values equal 0x7FFFFFFF.
    /// </para>
    /// <para>The Distance property is the cumulative distance traveled up to this 
    /// point in meters as determined by the wheel sensor or from the latitude/longitude 
    /// whichever is more accurate. If the distance cannot be obtained the property will 
    /// have a value of 1.0e25.
    /// </para>
    /// <para>
    /// A value of 0xFF for Cadence indicates that it is invalid.
    /// </para>
    /// <para>
    /// Two consecutive Trackpoints with invalid lat/long, altitude, heart rate, 
    /// distance and cadence indicate a pause in the Trackpoint recording.
    /// </para>
    /// </remarks>

	public class Trackpoint : DataObjectBase
	{
		//these three are used internally for storing shape file coordinates where a transformation has taken place
        private double m_y = 0;
        private double m_x = 0;
        internal bool Transformed = false;

		private int m_hashCode = -1;

        private double m_latitude = 0;
        private double m_longitude = 0;
        private bool m_newTrack = false;
        private int m_time = 0;
        private float m_altitude = 0;
        private float m_depth = 0;
        private float m_temperature = 0;
        private DateTime m_fixDateTime = DateTime.MinValue;
        private string m_name = "";

        private int m_heartRate = 0;
        private int m_cadence = 0;
        private bool m_sensor = false;
        private float m_distance = 0;

        /// <summary>
		/// Returns/Sets the Latitude co-ordinate in degrees.
		/// </summary>
		public double Latitude
        {
            get
            {
                return m_latitude;
            }
            set
            {
                m_latitude = value;
            }
        }

		/// <summary>
		/// Returns/Sets the Longtitude co-ordinate in degrees.
		/// </summary>
		public double Longitude
        {
            get
            {
                return m_longitude;
            }
            set
            {
                m_longitude = value;
            }
        }

        /// <summary>
        /// Returns/Sets the X co-ordinate. 
        /// </summary>
        public double X
        {
            get
            {
                return m_x;
            }
            set
            {
                m_x = value;
                Transformed = true;
            }
        }

        /// <summary>
        /// Returns/Sets the Y co-ordinate.
        /// </summary>
        public double Y
        {
            get
            {
                return m_y;
            }
            set
            {
                m_y = value;
                Transformed = true;
            }
        }

		/// <summary>
		/// Returns/Sets the new track segment.
		/// </summary>
		public bool NewTrack
        {
            get
            {
                return m_newTrack;
            }
            set
            {
                m_newTrack = value;
            }
        }

		/// <summary>
		/// Returns/Sets the time value. This value represents
        /// the number of seconds since December 31st 1989 and is
        /// used by some Garmin devices.
		/// </summary>
		public int Time
        {
            get
            {
                return m_time;
            }
            set
            {
                m_time = value;
            }
        }

		/// <summary>
		/// Returns/Sets the Altitude in metres.
		/// </summary>
		public float Altitude
        {
            get
            {
                return m_altitude;
            }
            set
            {
                m_altitude = value;
            }
        }

		/// <summary>
		/// Returns/Sets the Depth in metres.
		/// </summary>
		public float Depth
        {
            get
            {
                return m_depth;
            }
            set
            {
                m_depth = value;
            }
        }

		/// <summary>
		/// Returns/Sets the Temperature.
		/// </summary>
		public float Temperature
        {
            get
            {
                return m_temperature;
            }
            set
            {
                m_temperature = value;
            }
        }
        /// <summary>
        /// Returns/Sets the Heart rate in beats per minute.
        /// </summary>
        public int HeartRate
        {
            get
            {
                return m_heartRate;
            }
            set
            {
                m_heartRate = value;
            }
        }
        /// <summary>
        /// Returns/Sets the Cadence in revolutions per minute.
        /// </summary>
        public int Cadence
        {
            get
            {
                return m_cadence;
            }
            set
            {
                m_cadence = value;
            }
        }
        /// <summary>
        /// Returns/Sets the Sensor indicating whether a wheel sensor is present.
        /// </summary>
        public bool Sensor
        {
            get
            {
                return m_sensor;
            }
            set
            {
                m_sensor = value;
            }
        }
        /// <summary>
        /// Returns/Sets the distance traveled in meters.
        /// </summary>
        public float Distance
        {
            get
            {
                return m_distance;
            }
            set
            {
                m_distance = value;
            }
        }

        /// <summary>
        /// Returns/Sets the Fix Date and Time. This property is used by some Magellan Units only.
        /// </summary>
        public DateTime FixDateTime
        {
            get
            {
                return m_fixDateTime;
            }
            set
            {
                m_fixDateTime = value;
            }
        }

        /// <summary>
        /// Returns/Sets the Trackpoint Name. This property is used by some Magellan Units.
        /// </summary>
        public string Name
        {
            get
            {
                return m_name;
            }
            set
            {
                m_name = value;
            }
        }


		private const string XML_ROOT = "gpstrackpoint";
		private const string XML_LATITUDE = "latitude";
		private const string XML_LONGITUDE = "longitude";
		private const string XML_NEWTRACK = "newtrack";
		private const string XML_TIME = "time";
		private const string XML_ALTITUDE = "altitude";
		private const string XML_DEPTH = "depth";
		private const string XML_TEMPERATURE = "temperature";
		private const string XML_DATETIME_OF_FIX = "fixdatetime";
        //private const string XML_UTCTIME = "utctime";
        //private const string XML_UTCDATE = "utcdate";


		/// <summary>
		/// Returns an XML representation of the object.
		/// </summary>
		public string ToXml()
		{
			XmlDocument objXMLDOM = new XmlDocument();

			XmlElement objRootNode = null;
			
			try
			{
				//create the root node
				objRootNode = objXMLDOM.CreateElement(XML_ROOT);
			
				//append the root node to the document
				objXMLDOM.AppendChild(objRootNode);

				//create the child nodes
				AddXMLNode(objRootNode, XML_LATITUDE, Latitude.ToString(CultureInfo.InvariantCulture), false);
				AddXMLNode(objRootNode, XML_LONGITUDE, Longitude.ToString(CultureInfo.InvariantCulture), false);
				AddXMLNode(objRootNode, XML_NEWTRACK, NewTrack.ToString(CultureInfo.InvariantCulture), false);
				AddXMLNode(objRootNode, XML_TIME, Time.ToString(CultureInfo.InvariantCulture), false);
				AddXMLNode(objRootNode, XML_ALTITUDE, Altitude.ToString(CultureInfo.InvariantCulture), false);
				AddXMLNode(objRootNode, XML_DEPTH, Depth.ToString(CultureInfo.InvariantCulture), false);
				AddXMLNode(objRootNode, XML_TEMPERATURE, Temperature.ToString(CultureInfo.InvariantCulture), false);
				AddXMLNode(objRootNode, XML_DATETIME_OF_FIX, FixDateTime.ToString("U"), false); //output as UTC
                //TODO: Test the utc output ensure it is correct
                //AddXMLNode(objRootNode, XML_UTCTIME, utcTime.ToString("U"), false); //output as UTC
                //AddXMLNode(objRootNode, XML_UTCDATE, utcDate.ToString(CultureInfo.InvariantCulture), false); //output as UTC

			}
            catch (NullReferenceException e)
            {
				throw new XmlException(e.Message, e);
			}

			return objXMLDOM.OuterXml;
		}

		/// <summary>
		/// This method populates the object from XML.
		/// </summary>
        public void XmlLoad(string xml)
		{
			string strXPathRoot = "";
			XmlDocument objDOM = new XmlDocument();
            
			try
			{
                objDOM.LoadXml(xml);

				if(objDOM.FirstChild.Name == XML_ROOT)
				{
					strXPathRoot = string.Concat("//" , XML_ROOT , "/");

					//need to ignore type conversion errors and missing xml elements
					Latitude = ReadXMLNodeAsDouble(objDOM,string.Concat(strXPathRoot, XML_LATITUDE));
					Longitude = ReadXMLNodeAsDouble(objDOM,string.Concat(strXPathRoot, XML_LONGITUDE));
					NewTrack = ReadXMLNodeAsBoolean(objDOM, string.Concat(strXPathRoot, XML_NEWTRACK));
					Time = ReadXMLNodeAsInteger(objDOM, string.Concat(strXPathRoot, XML_TIME));
					Altitude = ReadXMLNodeAsSingle(objDOM, string.Concat(strXPathRoot, XML_ALTITUDE));
					Depth = ReadXMLNodeAsSingle(objDOM, string.Concat(strXPathRoot, XML_DEPTH));
					Temperature = ReadXMLNodeAsSingle(objDOM, string.Concat(strXPathRoot, XML_TEMPERATURE));
					
					//date is always stored as local time but xml is in UTC so convert back
					FixDateTime = System.DateTime.Parse(ReadXMLNodeAsString(objDOM, string.Concat(strXPathRoot, XML_DATETIME_OF_FIX)));	
					FixDateTime = FixDateTime.ToLocalTime();

                    ////date is always stored as local time but xml is in UTC so convert back
                    //utcTime = System.DateTime.Parse(ReadXMLNodeAsString(objDOM, string.Concat(strXPathRoot, XML_UTCTIME)));
                    //utcTime = utcTime.ToLocalTime();

                    ////date is always stored as local time but xml is in UTC so convert back
                    //utcDate = System.DateTime.Parse(ReadXMLNodeAsString(objDOM, string.Concat(strXPathRoot, XML_UTCDATE)));
                    //utcDate = utcDate.ToLocalTime();

				}
			}
            catch (NullReferenceException e)
            {
				throw new XmlException(e.Message ,e);
			}

		}
        /// <summary>
        /// Overridden method. Returns true of the values of each
        /// of the properties are equal in value.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>boolean</returns>
        public override bool Equals(object obj)
        {
            return Equals(obj, false);
        }
        /// <summary>
        /// Overridden method. Returns true of the values of each
        /// of the properties are equal in value. Passing true
        /// to the positionOnly parameter will compare the Trackpoint
        /// in terms of the Latitude and Longitude only.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="positionOnly"></param>
        /// <returns>boolean</returns>
        public bool Equals(object obj, bool positionOnly)
        {
            try
            {
                //check the type first
                if (obj.GetType() != this.GetType())
                    return false;

                //cast it
                Trackpoint objTrackpoint = (Trackpoint)obj;

                if (objTrackpoint.Latitude != Latitude) return false;
                if (objTrackpoint.Longitude != Longitude) return false;

                if (!positionOnly)
                {

                    if (objTrackpoint.Altitude != Altitude) return false;
                    if (objTrackpoint.Depth != Depth) return false;

                    //[jn] we only compare date and time to a resolution of one second
                    //as th=is is the level used by the ToXml and XmlLoad methods.
                    if (objTrackpoint.FixDateTime.Date != FixDateTime.Date) return false;
                    if (objTrackpoint.FixDateTime.Hour != FixDateTime.Hour) return false;
                    if (objTrackpoint.FixDateTime.Minute != FixDateTime.Minute) return false;
                    if (objTrackpoint.FixDateTime.Second != FixDateTime.Second) return false;

                    if (objTrackpoint.NewTrack != NewTrack) return false;
                    if (objTrackpoint.Temperature != Temperature) return false;
                    if (objTrackpoint.Time != Time) return false;
                    //if (objTrackpoint.FixDateTime != FixDateTime) return false;
                    //if (objTrackpoint.FixDateTime != FixDateTime) return false;
                }

            }
            catch
            {
                throw;
            }
			finally
			{
			}
			//if we get here all must be the same.
			return true;
		}
        /// <summary>
        /// Overridden function. Retrieves a value that indicates the hash code value for the object.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
		{

			//this value will be used for the overriden GetHashCode function and should
			//ensure that two object that are the same return the same Hash Code.
			//the hash code has to be imutable to is stored in a member variable.
			if( m_hashCode <= 0 )
				m_hashCode = Latitude.GetHashCode() ^ Longitude.GetHashCode();

			return m_hashCode;
		}

	}
}
